//
// @(#)XJSVGCanvas.java 2/2004
//
// Copyright 2004 Zachary DelProposto. All rights reserved.
// Use is subject to license terms.
//
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// Or from http://www.gnu.org/
//
package dip.gui.map;
import dip.gui.StatusBar;
import dip.gui.dialog.ErrorDialog;
import dip.misc.Utils;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.Container;
import java.awt.Window;
import java.awt.Cursor;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.ActionMap;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.SVGUserAgent;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.BridgeContext;
import java.awt.geom.AffineTransform;
import java.awt.event.ActionEvent;
import java.awt.Dimension;
import org.apache.batik.*;
import org.apache.batik.dom.*;
import org.apache.batik.util.*;
import org.w3c.dom.svg.*;
import org.w3c.dom.svg.SVGSVGElement;
import org.apache.batik.swing.gvt.GVTTreeRendererListener;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.bridge.ViewBox;
import org.apache.batik.gvt.CanvasGraphicsNode;
/**
* Provides for enhanced functionality over a standard JSVGCanvas.
* <p>
* Consists of a modified mouse/key listners and a special transform
* listener which allows the XSVGScroller to properly function.
* <p>
* Furthermore, it modifies the JSVGCanvas.ZOOM_OUT_ACTION and
* JSVGCanvas.ZOOM_IN_ACTION to reflect the scale factor, as set
* with setZoomScaleFactor().
*/
public class XJSVGCanvas extends JSVGCanvas
{
// constants
private static final String I18N_ZOOM_FACTOR = "XJSVGScroller.zoom.text";
private static final int MIN_DRAG_DELTA = 5; // min pixels to count as a drag
// instance variables
/** The default unit scroll increment */
private int unitIncrement = 10;
/** The default block scroll increment */
private int blockIncrement = 30;
/** Minimum scale value (if > 0.0) */
private double minScale = 0.0f;
/** Maximum scale value (if > 0.0) */
private double maxScale = 0.0f;
private Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
private boolean isValidating = false;
private double lsx,lsy; // last scale x, y values
private final StatusBar statusBar;
/**
* Creates a new XJSVGCanvas.
*
* @param ua a SVGUserAgent instance or null.
* @param eventsEnabled Whether the GVT tree should be reactive to mouse and
* key events.
* @param selectableText Whether the text should be selectable.
*/
public XJSVGCanvas(final MapPanel mapPanel, StatusBar statusBar, SVGUserAgent ua, boolean eventsEnabled, boolean selectableText)
{
super(ua, eventsEnabled, selectableText);
this.statusBar = statusBar;
setMaximumSize(screenSize);
// fix for incorrect setting of initial SVG size by JSVGScrollPane
addGVTTreeRendererListener(new GVTTreeRendererListener()
{
public void gvtRenderingCompleted(GVTTreeRendererEvent e)
{
AffineTransform iat = getInitialTransform();
SVGSVGElement elt = getSVGDocument().getRootElement();
if(iat != null || elt == null) // very very defensive... but iat null check is important
{
// rescale viewbox transform to reflect viewbox size
Dimension vbSize = mapPanel.getScrollerSize(); // size of the canvas' scrolling container (don't include scroll bars)
CanvasGraphicsNode cgn = getCanvasGraphicsNode();
// ViewBox.getViewTransform is essential for calculating the correct transform,
// AND accounting for any viewBox attribute of the root SVG element, if present.
AffineTransform vt = ViewBox.getViewTransform
(getFragmentIdentifier(), elt, vbSize.width, vbSize.height);
cgn.setViewingTransform(vt);
// set rendering transform to 'unscaled'
AffineTransform t = AffineTransform.getScaleInstance(1,1);
XJSVGCanvas.super.setRenderingTransform(t);
}
}// gvtRenderingCompleted()
public void gvtRenderingCancelled(GVTTreeRendererEvent e) {}
public void gvtRenderingFailed(GVTTreeRendererEvent e) {}
public void gvtRenderingPrepare(GVTTreeRendererEvent e) {}
public void gvtRenderingStarted(GVTTreeRendererEvent e) {}
});
}// XJSVGCanvas()
/** Sets if this is we should validate SVG or not */
public void setValidating(boolean value)
{
isValidating = value;
}// setValidating()
/**
* Overrides createListener() to return our own Listener, with
* several new features we need.
*
*/
protected Listener createListener()
{
return new XJSVGCanvasListener();
}// createListener()
/**
* Overrides createUserAgent() to return our own UserAgent, which
* allows selectable validation control of the parser.
*
*/
protected UserAgent createUserAgent()
{
return new XJSVGUserAgent();
}// createUserAgent()
/**
* Sets the parent component, to which key events are sent.
*
*/
public void setParent(Component c)
{
((XJSVGCanvasListener) listener).setParent(c);
}// setParent()
/**
* Specialized Listener that provides additional functionality.
* <p>
* New Features:
* <ul>
* <li>Short drags are interpreted as clicks
* <li>Key events can be passed to the parent (as defined by setParent())
* </ul>
*/
protected class XJSVGCanvasListener extends JSVGCanvas.CanvasSVGListener
{
private int dragX; // start drag X coord
private int dragY; // start drag Y coord
private boolean inDrag = false; // 'true' if we are in a drag (versus a click)
private Component parent = null; // parent component for events
public XJSVGCanvasListener()
{
}// XJSVGCanvasListener()
public void mouseDragged(MouseEvent e)
{
inDrag = true;
super.mouseDragged(e);
}// mouseDragged()
public void mousePressed(java.awt.event.MouseEvent e)
{
// set drag start coordinates
dragX = e.getX();
dragY = e.getY();
super.mousePressed(e);
}// mousePressed()
public void mouseReleased(java.awt.event.MouseEvent e)
{
if(inDrag)
{
int dx = Math.abs(e.getX() - dragX);
int dy = Math.abs(e.getY() - dragY);
if(dx < MIN_DRAG_DELTA && dy < MIN_DRAG_DELTA)
{
// our drag was short! dispatch a CLICK event.
//
MouseEvent click = new MouseEvent(
e.getComponent(),
MouseEvent.MOUSE_CLICKED,
e.getWhen(),
e.getModifiersEx(), // modifiers
e.getX(),
e.getY(),
e.getClickCount(),
e.isPopupTrigger(),
e.getButton() );
super.mouseClicked(click);
}
else
{
// not a short drag; return original event
super.mouseReleased(e);
}
}
// reset drag
inDrag = false;
}// mouseReleased()
public void keyPressed(java.awt.event.KeyEvent e)
{
if(parent != null)
{
parent.dispatchEvent(e);
}
super.keyPressed(e);
}// keyPressed()
public void keyReleased(java.awt.event.KeyEvent e)
{
if(parent != null)
{
parent.dispatchEvent(e);
}
super.keyReleased(e);
}// keyReleased()
public void keyTyped(java.awt.event.KeyEvent e)
{
if(parent != null)
{
parent.dispatchEvent(e);
}
super.keyTyped(e);
}// keyTyped()
/** Set parent to receive key events; null if none. */
public void setParent(Component c)
{
parent = c;
}// setParent()
}// nested class XJSVGCanvasListener
/**
* Specialized UserAgent that checks outer class for validation parameter
* and subclasses error and message dialogs.
*/
protected class XJSVGUserAgent extends JSVGCanvas.CanvasUserAgent
{
public XJSVGUserAgent()
{
super();
}// XJSVGUserAgent()
public boolean isXMLParserValidating()
{
return XJSVGCanvas.this.isValidating;
}// isXMLParserValidating()
/**
* Do nothing. We don't want the Batik
* CursorManager updating our cursor.
*/
public void setSVGCursor(Cursor c)
{
// do nothing.
}// setSVGCursor()
/** Displays an SVG error Exception using an ErrorDialog */
public void displayError(Exception ex)
{
ErrorDialog.displaySerious(findParent(), ex);
}// displayError()
/** Displays an SVG error String using an ErrorDialog */
public void displayError(String message)
{
ErrorDialog.displaySerious(findParent(), new Exception(message));
}// message()
/** Find the parent frame, if possible. */
private JFrame findParent()
{
// find parent frame, if possible
Component comp = XJSVGCanvas.this.getParent();
while(comp != null)
{
if(comp instanceof JFrame)
{
return (JFrame) comp;
}
comp = comp.getParent();
}
return null;
}// findParent()
}// nested class XJSVGUserAgent
/**
* Calls GVTTransformListener.transformChanged(), after setting
* the rendering transform of the JSVGCanvas.
*
* @param at an AffineTransform.
*/
public void setRenderingTransform(AffineTransform at)
{
// check to see that we are not zooming too little
if(minScale > 0.0 && (at.getScaleX() < minScale || at.getScaleY() < minScale))
{
return; // reject transform
}
// check to see that we are not zooming too much
if(maxScale > 0.0 && (at.getScaleX() > maxScale || at.getScaleY() > maxScale))
{
return; // reject transform
}
if(!isEquivalent(lsx, at.getScaleX()))
{
statusBar.setText(Utils.getLocalString(I18N_ZOOM_FACTOR, new Double(at.getScaleX())));
}
else if(!isEquivalent(lsy, at.getScaleY()))
{
statusBar.setText(Utils.getLocalString(I18N_ZOOM_FACTOR, new Double(at.getScaleY())));
}
lsx = at.getScaleX();
lsy = at.getScaleY();
// proceed with setting the rendering transform...
super.setRenderingTransform(at);
}// setRenderingTransform()
/** Sets the minimum allowable scale size. 1.0 == no scaling. any negative value or 0 disables. */
public void setMinimumScale(double value)
{
minScale = value;
}// setMinimumScale()
/** Sets the maximum allowable scale size. 1.0 == no scaling. any negative value or 0 disables. */
public void setMaximumScale(double value)
{
maxScale = value;
}// setMaximumScale()
/**
* Sets the zoom in/out scale factor to that given. For example, if
* set to 2.0, zoom in will be x2 and zoom out will be 0.5 (1/2).
*/
public void setZoomScaleFactor(float scaleFactor)
{
final ActionMap actionMap = getActionMap();
actionMap.put(JSVGCanvas.ZOOM_IN_ACTION, new ZoomAction(scaleFactor));
actionMap.put(JSVGCanvas.ZOOM_OUT_ACTION, new ZoomAction(1.0 / scaleFactor));
}// setZoomScaleFactor()
/** Test for floating-point "equivalence" */
private boolean isEquivalent(double a, double b)
{
return(Math.abs(a-b) <= 0.0001);
}// isEquivalent()
}// class XJSVGCanvas